//-------------------------------------------------------------------------------------
// <copyright file="Install.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation. All rights reserved.
//      THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
//      EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 
//      WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// </copyright>
// <author>Greg Beech</author>
// <email>gregbee@microsoft.com</email>
// <date>2004-04-15</date>
// <summary>
//      Installs an MSI package.
// </summary>  
//-------------------------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.Msi
{
  #region Using directives

  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Diagnostics;
  using System.Globalization;
  using System.IO;
  using System.Text;
  using System.Xml;
  using System.Runtime.InteropServices;
  using Microsoft.Build.Framework;

  #endregion

  #region Class Comments
  /// <summary>
  /// Installs an MSI package.
  /// </summary>
  /// <remarks>
  /// <code><![CDATA[<Msi.Install MsiPath="msiPath" ProductCode="productCode" LogFile="logFile" LogOptions="logOptions" UserInterfaceLevel="userInterfaceLevel" LoadUserProfile="loadUserProfile" PropertyFiles="propertyFiles" Properties="properties" Features="features" />]]></code>
  /// <para>where:</para>
  /// <para><i>msiPath (Required*)</i></para>
  /// <para>
  /// The path to the MSI to install.
  /// </para>
  /// <para><i>productCode (Required*)</i></para>
  /// <para>
  /// The GUID product code of the product to install.
  /// </para>
  /// <para><i>logFile (Required)</i></para>
  /// <para>
  /// The path to the log file for the installation. The recommended file name is the name of the MSI
  /// with the extension *.install.log.
  /// </para>
  /// <para><i>logOptions</i></para>
  /// <para>
  /// A delimited list of logging options. Possible values are: Default, StatusMessages, Warnings, Errors,
  /// ActionStart, ActionSpecificRequests, UserRequests, InitialParameters, OutOfMemory, TerminalProperties,
  /// Verbose, AppendLog, FlushEachLine, AllInformation. The default is Default. Please see the
  /// <see cref="Microsoft.Sdc.Tasks.Msi.MsiTaskBase.MsiLoggingOptions"/> documentation for
  /// further information.
  /// </para>
  /// <para><i>userInterfaceLevel</i></para>
  /// <para>
  /// The user interface level displayed by MsiExec during the installation. Possible values are: 
  /// None, Basic, ReducedWithModalDialog, FullWithModalDialog, NoneWithModalDialog, BasicWithModalDialog,
  /// BasicWithNoModalDialogs. The default is None. Please see the 
  /// <see cref="Microsoft.Sdc.Tasks.Msi.MsiTaskBase.MsiExecUILevel"/> documentation for
  /// further information.
  /// </para>
  /// <para><i>loadUserProfile</i></para>
  /// <para>
  /// <b>true</b> if the user's profile is loaded during the processing of the MSI, or <b>false</b> otherwise.
  /// This may be needed for some custom installation actions such as configuring ports and channels in 
  /// Microsoft BizTalk Server. The default is <b>false</b>.
  /// </para>
  /// <para><i>propertyFiles</i></para>
  /// <para>
  /// A delimited list of XML property files that provide installation properties for the MSI. 
  /// Multiple property files are allowed so that you can have a hierarchy - the first file provides a base set of properties
  /// (e.g. environmental properties for a particular rig) and subsequent files can override these to provide a more
  /// specific property or add new properties (e.g. properties for a machine, then properties for an individual MSI).
  /// </para>
  /// <para><i>properties</i></para>
  /// <para>
  /// A delimited list of installation properties in the format Name=Value. If any property files have been 
  /// specified, then the properties specified here are seen as the most significant and can overwrite 
  /// any of the property values specified in the files.
  /// </para>
  /// <para><i>features</i></para>
  /// <para>
  /// A delimited list of the names of features to be installed. This is not necessary if the MSI does not
  /// have features configured, or you wish all features to be installed.
  /// </para>
  /// <para>
  /// <b>*</b> - Either <i>msiPath</i> or <i>productCode</i> must be provided. If both are provided then 
  /// <i>msiPath</i> takes precedence and <i>productCode</i> is ignored. If neither are provided then the
  /// task will throw an exception when it executes.
  /// </para>
  /// </remarks>
  /// <example>
  /// <code><![CDATA[
  /// <Project>
  ///     <Target Name="Test">
  ///         <Msi.Install 
  ///             MsiPath="C:\Build\Msi\Packages\MyPackage.msi" 
  ///             ProductCode="b6b6f41c-fd3e-4b5b-8a73-c59494974990" 
  ///             LogFile="C:\Build\Msi\Packages\MyPackage.install.log" 
  ///             LogOptions="Default;Verbose" 
  ///             UserInterfaceLevel="None" 
  ///             LoadUserProfile="true" 
  ///             PropertyFiles="C:\Build\Properties\Global.xml;C:\Build\Properties\Rig4.xml" 
  ///             Properties="INSTALLDIR=C:\Install;DOMAIN=rig4;MACHINE=r4app01" 
  ///             Features="MSMQ,BTS2004,WebServices" />
  ///     </Target>
  /// </Project>
  /// ]]></code>  
  /// A sample property file is shown below. Note that you must include the namespace for the file to
  /// be parsed correctly.
  /// <code><![CDATA[
  /// <Properties xmlns="urn:sdc-microsoft-com:deployment:properties:v2.0">
  ///     <Property Name="MY_PROPERTY" Value="some value" />
  ///     <Property Name="ANOTHER_PROPERTY" Value="some other value" />
  ///     <Property Name="SOMETHING_ELSE" Value="hello world" />
  ///     ... etc ...
  /// </Properties>
  /// ]]></code>
  /// </example>
  #endregion
  public sealed class GetProperty : TaskBase
  {
    #region Member Variables

    private string propertyName = null;
      private string propertyValue = null;
      private string msiPath = null;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the InstallMsi class.
    /// </summary>
      public GetProperty()
    {
    }

    #endregion

    #region Properties

    /// <summary>
    /// Specifies the MSI property to extract
    /// </summary>
    /// <value>
    /// The name of the property
    /// </value>
    public string PropertyName
    {
      get
      {
          return this.propertyName;
      }
      set
      {
          this.propertyName = value;
      }
    }

    /// <summary>
    /// Gets or sets the full or relative path to the MSI.
    /// </summary>
    /// <value>
    /// The full or relative path to the MSI.
    /// </value>
    public string MsiPath
    {
        get
        {
            return (this.msiPath == null) ? string.Empty : this.msiPath;
        }

        set
        {
            this.msiPath = value;
        }
    }
    /// <summary>
    /// Returns the value of the MSI property requested
    /// </summary>
    /// <value>
    /// value of the property
    /// </value>    
    [Output]
    public string PropertyValue
    {
      get
      {
        return propertyValue;
      }

      set
      {
        propertyValue = value;
      }
    }
    #endregion

    #region Protected Methods

    /// <summary>
    /// Executes the task to extract an MSI property.
    /// </summary>
    /// <summary>
    /// Performs the action of this task.
    /// </summary>
    protected override void InternalExecute()
    {
      if (!string.IsNullOrEmpty(this.MsiPath))
      {
          ComWrapper installer = new ComWrapper("WindowsInstaller.Installer");
          ComWrapper msiDatabase = new ComWrapper(installer.CallMethod("OpenDatabase", new object[] { this.MsiPath, 1 /* WindowsInstaller.MsiOpenDatabaseMode.msiOpenDatabaseModeTransact */}));

          this.propertyValue = GetPropertyRecord(msiDatabase, this.propertyName);

          // We need to free the connection to the database so that the msi can be loaded by the msiexec tool.
          msiDatabase.ComObject = null;
          msiDatabase = null;
          installer.ComObject = null;
          installer = null;

          // Make sure the installer' database object has been freed.
          GC.Collect();
      }
      else
      {
        throw new InvalidOperationException("the MSI path must be specified");
      }
    }

    #endregion

    #region Private Methods

    private string GetPropertyRecord(ComWrapper msiDatabase /* WindowsInstaller.Database msiDatabase */, string propertyName)
    {
      string localPropertyValue = null;
      ComWrapper propertyView; // WindowsInstaller.View propertyView;
      string msiSQLStatement;

      msiSQLStatement = "SELECT * FROM Property WHERE Property = '" + propertyName + "'";
      propertyView = new ComWrapper(msiDatabase.CallMethod("OpenView", new object[] { msiSQLStatement }));
      propertyView.CallMethod("Execute", new object[] { null });
      ComWrapper pr = new ComWrapper(propertyView.CallMethod("Fetch"));

      if (pr == null)
      {
        // Property DOES NOT exist in database leave output as null    
      }
      else
      {
          localPropertyValue = (string)pr.GetProperty("StringData", 2);
      }
      propertyView = new ComWrapper(msiDatabase.CallMethod("OpenView", new object[] { msiSQLStatement }));
      propertyView.CallMethod("Execute", new object[] { null });
      propertyView.CallMethod("Close");

      return localPropertyValue;
    }
    #endregion
  }
}